package org.gdstash.file;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
import java.util.List;

public class GDByteBuffer extends GDAbstractBuffer {
    private int bufferSize;
    private int lastBlockSize;
    private int fileSize;
    private List<byte[]> list;
    private int pos;
    private ByteOrder bo;

    public GDByteBuffer(final String filename) {
        this(new File(filename), 104857600);
    }

    public GDByteBuffer(final String filename, final int bufferSize) {
        this(new File(filename), bufferSize);
    }

    public GDByteBuffer(final File file) {
        this(file, 104857600);
    }

    public GDByteBuffer(final File file, final int bufferSize) {
        this.bufferSize = bufferSize;
        this.list = new LinkedList<byte[]>();
        try (final RandomAccessFile raf = new RandomAccessFile(file, "r")) {
            this.fileSize = (int) raf.length();
            int rest = this.fileSize;
            int size = 0;
            if (rest < bufferSize) {
                size = rest;
                this.lastBlockSize = rest;
            } else {
                size = bufferSize;
            }
            while (size > 0) {
                final byte[] bytes = new byte[size];
                raf.readFully(bytes);
                this.list.add(bytes);
                rest -= size;
                if (rest < bufferSize) {
                    if ((size = rest) <= 0) {
                        continue;
                    }
                    this.lastBlockSize = rest;
                } else {
                    size = bufferSize;
                }
            }
        } catch (IOException ex) {
            this.list = new LinkedList<byte[]>();
        }
        this.pos = 0;
        this.bo = ByteOrder.LITTLE_ENDIAN;
    }

    @Override
    public byte[] getByteArray(final int length) throws IOException {
        return this.getByteArray(this.pos, length);
    }

    @Override
    public byte[] getByteArray(final long pos, final int length) throws IOException {
        if (pos > 2147483647L) {
            throw new EOFException();
        }
        final int listSize = (this.list.size() - 1) * this.bufferSize + this.lastBlockSize;
        if (listSize < pos) {
            return null;
        }
        int byteSize = length;
        if (listSize < pos + length) {
            byteSize = listSize - (int) pos;
        }
        if (byteSize <= 0) {
            return null;
        }
        final byte[] result = new byte[byteSize];
        int tempPos = (int) pos;
        int tempLen = length;
        int resOffset = 0;
        int listPos = 0;
        for (final byte[] bytes : this.list) {
            if (listPos <= tempPos && listPos + bytes.length > tempPos) {
                final int bufferOffset = tempPos - listPos;
                int maxPos = bufferOffset + tempLen;
                if (maxPos > bytes.length) {
                    maxPos = bytes.length;
                }
                for (int i = bufferOffset; i < maxPos; ++i) {
                    result[i - bufferOffset + resOffset] = bytes[i];
                }
                listPos += this.bufferSize;
                if (listPos > tempPos + tempLen) {
                    break;
                }
                tempPos = listPos;
                tempLen -= maxPos - bufferOffset;
                resOffset += maxPos - bufferOffset;
            } else {
                listPos += this.bufferSize;
            }
        }
        this.pos = tempPos + tempLen;
        return result;
    }

    @Override
    public byte getByte() throws IOException {
        final byte[] bytes = this.getByteArray(1);
        return bytes[0];
    }

    @Override
    public int getInt() throws IOException {
        final byte[] bytes = this.getByteArray(4);
        return ByteBuffer.wrap(bytes).order(this.bo).getInt();
    }

    @Override
    public long getLong() throws IOException {
        final byte[] bytes = this.getByteArray(8);
        return ByteBuffer.wrap(bytes).order(this.bo).getLong();
    }

    @Override
    public short getShort() throws IOException {
        final byte[] bytes = this.getByteArray(2);
        return ByteBuffer.wrap(bytes).order(this.bo).getShort();
    }

    @Override
    public long getPosition() {
        return this.pos;
    }

    @Override
    public void setPosition(final long pos) throws IOException {
        if (pos > 2147483647L) {
            throw new EOFException();
        }
        this.pos = (int) pos;
    }

    @Override
    public void close() {
        this.list = null;
    }
}
